/*
	Written By Pradipna Nepal
	www.pradsprojects.com

	Copyright (C) 2012 Prads Projects
	Please read COPYING.txt included along with this source code for more detail.
	If not included, see http://www.gnu.org/licenses/
*/

#include "libCodeGeneration.h"

struct Label {
	int lbl, address;
	Label(int lbl, int address) {
		this->lbl = lbl;
		this->address = address;
	}
};

struct LblSortFunc {
	bool operator () (Label a, Label b) {
		return a.lbl < b.lbl;
	}
};

void resolveAddress(list<Opcode> *opMod, const IdentTable *identTable, unsigned int binCounter);
void getLabelAddress(const list<Opcode> *opMod, vector<Label> *lblAddress);
const IdentEntry *getIdent(const IdentTable *identTable, string identName);
int getIdentIndex(const IdentTable *identTable, string identName);
int getIdentSize(const IdentTable *identTable, string identName);
int calculateGlobalVarSize(const IdentTable *identTable);
int getFontBin(unsigned char *bin);
void putConstStringToBin(const IdentTable *identTable, unsigned char *bin, int globalVarOffset);
void putImagesToBin(const IdentTable *identTable, unsigned char *bin, int globalVarOffset, const char *sourcePath);

//Opcodes
void ldm_ry(const vector<IdentTable> *identTable, Opcode *op, unsigned char *binOut, unsigned int &binCounter, unsigned int index, int globalVarOffset);

void CodeGenerator::generateCode(const vector<TokenModule> *tMod, const SblTable *sblTable, vector<list<Opcode>> *opMod, unsigned char *binOut,
				string *startAddress, const char *sourcePath) {
	int globalVarSize = calculateGlobalVarSize(&sblTable->identTable[0]);
	int fontRoutineSize = getFontBin(binOut);
	putConstStringToBin(&sblTable->identTable[0], binOut, FONT_SIZE + fontRoutineSize);
	putImagesToBin(&sblTable->identTable[0], binOut, FONT_SIZE + fontRoutineSize, sourcePath);
	unsigned int binCounter = FONT_SIZE + fontRoutineSize + globalVarSize + 4;	//Just below fonts, global variables and base pointer initialization
	vector<Label> lblAddress;
	unsigned int opcode = 0;

	stringstream ss;
	ss << (binCounter - 4);
	*startAddress = ss.str();

	optimize(opMod);

	for (unsigned int i = 0; i < tMod->size(); ++i) {
		if ((*tMod)[i].name == "main") {
			resolveAddress(&(*opMod)[i], &sblTable->identTable[i], binCounter);
			getLabelAddress(&(*opMod)[i], &lblAddress);
			for (list<Opcode>::iterator it = (*opMod)[i].begin(); it != (*opMod)[i].end(); ++it) {
				binCounter = binCounter;
				switch (it->ir) {
					case NOP:
						opcode = 0;
						writeToBin(binCounter, opcode, binOut);
						break;
					case BGC:
						opcode = (atoi(it->operand[0].getValue().c_str()) << 16) | BGC;
						writeToBin(binCounter, opcode, binOut);
						break;
					case JMP_HHLL:
						opcode = (lblAddress[it->label >> 16].address << 16) | JMP_HHLL;
						writeToBin(binCounter, opcode, binOut);
						break;
					case JX:
						opcode = (lblAddress[it->label >> 16].address << 16) | (it->regs << 8) | JX;
						writeToBin(binCounter, opcode, binOut);
						break;
					case LDI_RX_HHLL:
						opcode = (atoi(it->operand[0].getValue().c_str()) << 16) | (it->regs << 8) | LDI_RX_HHLL;
						writeToBin(binCounter, opcode, binOut);
						break;
					case LDM_RY:
						{
							ldm_ry(&sblTable->identTable, &*it, binOut, binCounter, i, FONT_SIZE + fontRoutineSize);
						}
						break;
					case STM_RY:
						{
							opcode = getIdentIndex(&sblTable->identTable[0], it->operand[0].getValue());
							if (opcode == IDENT_NOT_FOUND) {	//If local variable
								opcode = 0xFE24;	//MOV RE, RF	- RF is used as base pointer
								writeToBin(binCounter, opcode, binOut);

								opcode = (getIdentIndex(&sblTable->identTable[i], it->operand[0].getValue()) << 16) | 0xE40;	//ADDI RE, HHLL
								writeToBin(binCounter, opcode, binOut);
						
								opcode = 0xE000 | (it->regs << 8) | STM_RY;
								writeToBin(binCounter, opcode, binOut);
							} else {	//If global variable
								opcode = ((opcode + FONT_SIZE + fontRoutineSize) << 16) | (it->regs << 8) | STM_RY;
								writeToBin(binCounter, opcode, binOut);
							}
						}
						break;
					case ADD:
						opcode = (it->regs << 8) | ADD;
						writeToBin(binCounter, opcode, binOut);
						break;
					case SUB:
						opcode = (it->regs << 8) | SUB;
						writeToBin(binCounter, opcode, binOut);
						break;
					case CMP:
						opcode = (it->regs << 8) | CMP;
						writeToBin(binCounter, opcode, binOut);
						break;
					case CMPI:
						opcode = (atoi(it->operand[0].getValue().c_str()) << 16) | (it->regs << 8) | CMPI;
						writeToBin(binCounter, opcode, binOut);
						break;
					case AND:
						opcode = (it->regs << 8) | AND;
						writeToBin(binCounter, opcode, binOut);
						break;
					case TSTI:
						opcode = (atoi(it->operand[0].getValue().c_str()) << 16) | (it->regs << 8) | TSTI;
						writeToBin(binCounter, opcode, binOut);
						break;
					case OR:
						opcode = (it->regs << 8) | OR;
						writeToBin(binCounter, opcode, binOut);
						break;
					case XOR:
						opcode = (it->regs << 8) | XOR;
						writeToBin(binCounter, opcode, binOut);
						break;
					case MUL:
						opcode = (it->regs << 8) | MUL;
						writeToBin(binCounter, opcode, binOut);
						break;
					case DIV:
						opcode = (it->regs << 8) | DIV;
						writeToBin(binCounter, opcode, binOut);
						break;
					case PRNT:
						{
							int cStringAddr = getIdentIndex(&sblTable->identTable[0], it->operand[0].getValue()) + FONT_SIZE + fontRoutineSize;
							int x = it->regs & 0xFFFF;
							int y = it->regs & 0xFFFF0000;

							opcode = (cStringAddr << 16) | LDI_RX_HHLL;
							writeToBin(binCounter, opcode, binOut);

							opcode = (x << 16) | 0x300 | LDI_RX_HHLL;
							writeToBin(binCounter, opcode, binOut);

							opcode = y | 0x400 | LDI_RX_HHLL;
							writeToBin(binCounter, opcode, binOut);

							opcode = 0xBE00000 | CALL_HHLL;
							writeToBin(binCounter, opcode, binOut);
						}
						break;
					case DRAW:
						{
							int imgSize = getIdentSize(&sblTable->identTable[0], it->operand[0].getValue());
							int width = (imgSize >> 16) / 2, height = imgSize & 0xFFFF;

							opcode = (height << 24) | (width << 16) | SPR;
							writeToBin(binCounter, opcode, binOut);

							for (int j = 1; j <= 2; ++j) { 
								if (it->operand[j].getToken() == TOKEN_IDENTIFIER) {
									Opcode temp;
									temp.ir = LDM_RY;
									temp.operand.push_back(it->operand[j]);
									temp.regs = j - 1;
									ldm_ry(&sblTable->identTable, &temp, binOut, binCounter, i, FONT_SIZE + fontRoutineSize);
								} else {
									int immi = atoi(it->operand[j].getValue().c_str());
									opcode = (immi << 16) | ((j - 1) << 8) | LDI_RX_HHLL;
									writeToBin(binCounter, opcode, binOut);
								}
							}

							int imgAddr = getIdentIndex(&sblTable->identTable[0], it->operand[0].getValue()) + FONT_SIZE + fontRoutineSize;
							opcode = (imgAddr << 16) | 0x1000 | 5;
							writeToBin(binCounter, opcode, binOut);
						}
						break;
					case CLS:
						opcode = 1;
						writeToBin(binCounter, opcode, binOut);
						break;
					case CTRL1:
						opcode = (0xFFF0 << 16) | (it->regs << 8) | LDM_HHLL;
						writeToBin(binCounter, opcode, binOut);
						break;
					case VBLNK:
						opcode = VBLNK;
						writeToBin(binCounter, opcode, binOut);
						break;
					case RND:
						opcode = (0xFFFF << 16) | (it->regs << 8) | RND;
						writeToBin(binCounter, opcode, binOut);
						break;
					case FLIP:
						{
							int orientation = atoi(it->operand[0].getValue().c_str());
							opcode = (orientation << 24) | FLIP;
							writeToBin(binCounter, opcode, binOut);
						}
				}
			}
			opcode = (binCounter << 16) | JMP_HHLL;		//Jump to it's own address
			writeToBin(binCounter, opcode, binOut);
		}
	}
	opcode = (binCounter << 16) | 0xF20;	//LDI R15, binCounter - Initializing baseCounter to the end of the binary code
	unsigned int globalVars = FONT_SIZE + fontRoutineSize + globalVarSize;
	writeToBin(globalVars, opcode, binOut);
}

void resolveAddress(list<Opcode> *opMod, const IdentTable *identTable, unsigned int binCounter) {
	int address = binCounter;

	for (list<Opcode>::iterator it = opMod->begin(); it != opMod->end(); ++it) {
		it->address = address;
		switch (it->ir) {
			case LDM_RY:
			case STM_RY:
				address += 12;		//LDM_RY IR take 3 opcodes
				break;
			case PRNT:
				address += 16;	//PRNT IR takes 4 opcodes
				break;
			case DRAW:
				for (int i = 1; i <= 2; ++i) {
					if (it->operand[i].getToken() == TOKEN_IDENTIFIER)
						address += 16;
					else
						address += 8;
				}
				break;
			default:
				address += 4;	//All the other IR takes 1 opcode
		}
	}
}

void getLabelAddress(const list<Opcode> *opMod, vector<Label> *lblAddress) {
	lblAddress->push_back(Label(0, 0));	//label cannot be 0
	for (list<Opcode>::const_iterator it = opMod->begin(); it != opMod->end(); ++it) {
		if (it->label & 0xFFFF)	{
			lblAddress->push_back(Label(it->label & 0xFFFF, it->address & 0xFFFF));
		}
	}
	sort(lblAddress->begin(), lblAddress->end(), LblSortFunc());
}

const IdentEntry *getIdent(const IdentTable *identTable, string identName) {
	pair<IdentTable::const_iterator, IdentTable::const_iterator> rangeIt;
	rangeIt = identTable->equal_range(fnvHash(identName.c_str(), identName.size()));
	for (IdentTable::const_iterator cIt = rangeIt.first; cIt != rangeIt.second; ++cIt) {
		if (cIt->second.name == identName) {
			return &cIt->second;
		}
	}

	return 0;
}

int getIdentIndex(const IdentTable *identTable, string identName) {
	const IdentEntry *identPtr = getIdent(identTable, identName);
	
	return (identPtr) ? identPtr->index : IDENT_NOT_FOUND;
}

int getIdentSize(const IdentTable *identTable, string identName) {
	const IdentEntry *identPtr = getIdent(identTable, identName);
	
	return (identPtr) ? identPtr->size : IDENT_NOT_FOUND;
}

int calculateGlobalVarSize(const IdentTable *identTable) {
	int retVal = 0;

	for (IdentTable::const_iterator it = identTable->cbegin(); it != identTable->cend(); ++it) {
		if (it->second.type == "Image")
			retVal += ((it->second.size >> 16) / 2) * (it->second.size & 0xFFFF);
		else
			retVal += it->second.size;
	}

	return retVal;
}

int getFontBin(unsigned char *bin) {
	ifstream fontFile;
	
	fontFile.open("bin/font.bin", ios::binary);
	fontFile.read((char*)bin, FONT_SIZE);
	fontFile.close();

	int size = 0;
	fontFile.open("bin/fontRoutine.bin", ios::binary | ios::ate);
	size = fontFile.tellg();
	if (size == -1) return 0;	//If error
	fontFile.seekg(0, ios::beg);
	fontFile.read((char*)&bin[FONT_SIZE], size);
	fontFile.close();

	return size;
}

void putConstStringToBin(const IdentTable *identTable, unsigned char *bin, int globalVarOffset) {
	for (IdentTable::const_iterator it = identTable->cbegin(); it != identTable->cend(); ++it) {
		if (!it->second.type.compare("ConstString")) {
			memcpy(&bin[globalVarOffset + it->second.index], it->second.initialValue.c_str(), it->second.initialValue.size() + 1);
		}
	}
}

void putImagesToBin(const IdentTable *identTable, unsigned char *bin, int globalVarOffset, const char *sourcePath) {
	for (IdentTable::const_iterator cIt = identTable->cbegin(); cIt != identTable->cend(); ++cIt) {
		if (cIt->second.type == "Image") {
			ifstream imageFile;
			string path;
			path = sourcePath;
			path += "/";
			path += cIt->second.initialValue.c_str();

			imageFile.open(path.c_str(), ios::binary);
			imageFile.read((char*)&bin[cIt->second.index + globalVarOffset], ((cIt->second.size >> 16) / 2) * (cIt->second.size & 0xFFFF));
			imageFile.close();
		}
	}
}

void ldm_ry(const vector<IdentTable> *identTable, Opcode *op, unsigned char *binOut, unsigned int &binCounter, unsigned int index, int globalVarOffset) {
	//TODO: CHECK IF IDENT EXIST OR NOT
	unsigned int opcode = getIdentIndex(&(*identTable)[0], op->operand[0].getValue());
	if (opcode == IDENT_NOT_FOUND) { //If local variable
		opcode = 0xFE24;	//MOV RE, RF	- RF is used as base pointer
		writeToBin(binCounter, opcode, binOut);

		opcode = (getIdentIndex(&(*identTable)[index], op->operand[0].getValue()) << 16) | 0xE40;	//ADDI RE, HHLL
		writeToBin(binCounter, opcode, binOut);

		opcode = 0xE000 | (op->regs << 8) | LDM_RY;
		writeToBin(binCounter, opcode, binOut);
	} else {	//If global variable
		opcode = ((opcode + globalVarOffset) << 16) | (op->regs << 8) | LDM_HHLL;
		writeToBin(binCounter, opcode, binOut);
	}
}